package com.common.mybatis.dict;

import com.common.mybatis.dict.annotation.EnumDictAnnotation;
import com.common.mybatis.dict.domain.EnumDict;
import com.common.mybatis.dict.mapper.EnumDictMapper;
import com.common.mybatis.enumeration.BaseEnum;
import com.google.common.collect.Lists;
import com.google.common.reflect.ClassPath;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;

import javax.inject.Inject;
import javax.inject.Named;
import java.io.IOException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.LongAdder;
import java.util.stream.Collectors;

@Slf4j
@Named
public class EnumDictManager {

    //扫描为规范enum，必要含此属性
    private final String ENUM_INDEX = "index";
    private final String ENUM_DESCRIPTION = "description";

    @Inject
    private EnumDictMapper enumDictMapper;

    /**
     * 刷新入库
     * @param packageNames
     */
    public void refreshEnumDict(String[] packageNames){
        log.info("扫描枚举信息同步数据库,包路径:[{}]",new Gson().toJson(packageNames));
        List<EnumDict> dbDictList = enumDictMapper.queryAllList();
        Map<String,EnumDict> dictMap = dbDictList.parallelStream()
                .collect(Collectors.toConcurrentMap(k->k.getClassName()+"#"+k.getEnumIndex(),v->v,(o,n)->n));
        LongAdder total = new LongAdder();
        scanEnumList(packageNames).parallelStream().forEach(dict->{
            String key = dict.getClassName()+"#"+dict.getEnumIndex();
            if (dictMap.containsKey(key)){
                dict.setId(dictMap.get(key).getId());
                enumDictMapper.update(dict);
            } else {
                enumDictMapper.insert(dict);
                total.increment();
            }
        });
        log.info("同步枚举字典表信息，库纪录数:[{}],新增枚举数:[{}]",dictMap.size(), total.intValue());
    }

    /**
     * 扫描加载所有BaseEnum实现枚举,并带有EnumDictAnnotation注解
     * @param packageNames
     * @return
     */
    public List<EnumDict> scanEnumList(String[] packageNames){
        Map<String, EnumDict> dictMap = new ConcurrentHashMap<>();

        for (String packageName : packageNames) {
            findClasses(packageName).stream().forEach(enumClass->{
                EnumDictAnnotation annotation = (EnumDictAnnotation) enumClass.getAnnotation(EnumDictAnnotation.class);
                if (Objects.nonNull(annotation)){
                    String clazzName = enumClass.getSimpleName();
                    String annotationValue = annotation.value();

                    List<String> methodList = Arrays.stream(enumClass.getMethods()).map(m->m.getName()).collect(Collectors.toList());
                    Map<String,String> fieldMethodMap = new ConcurrentHashMap<>();
                    Arrays.stream(enumClass.getDeclaredFields()).forEach(f->{
                        String getMethod = "get"+getMethodName(f.getName());
                        if (methodList.contains(getMethod)){
                            fieldMethodMap.put(f.getName(), getMethod);
                        }
                    });

                    for (Object e : enumClass.getEnumConstants()) {
                        EnumDict dict = new EnumDict();
                        dict.setClassName(clazzName);
                        dict.setDescription(annotationValue);
                        dict.setEnumName(e.toString());
                        Map<String,String> futures = new HashMap<>();
                        fieldMethodMap.forEach((k, v) -> {
                            try {
                                Method method = enumClass.getMethod(v);
                                String value = method.invoke(e).toString();
                                switch (k){
                                    case ENUM_INDEX: dict.setEnumIndex(Integer.valueOf(value)); break;
                                    case ENUM_DESCRIPTION: dict.setEnumDescription(value); break;
                                    default: futures.put(k, value);
                                }
                            } catch (Exception ex) {
                                ex.printStackTrace();
                            }
                        });
                        dict.setFuturesJson(new Gson().toJson(futures));
                        dict.setCreateTime(LocalDateTime.now());
                        dict.setUpdateTime(LocalDateTime.now());

                        String key = clazzName+"#"+dict.getEnumIndex();
                        if (dictMap.containsKey(key)){
                            log.info("enumDict.conflict:枚举内容有冲突,已被覆盖,enum:[{}],index:[{}]", clazzName, dict.getEnumIndex());
                        }
                        dictMap.put(key,dict);
                    }
                }
            });
        }

        return Lists.newArrayList(dictMap.values());
    }

    private static String getMethodName(String fieldName){
        char[] chars = fieldName.toCharArray();
        chars[0] = toUpperCase(chars[0]);
        return String.valueOf(chars);
    }
    private static char toUpperCase(char c){
        if (c>= 97 && c<= 122){
            c = Character.toUpperCase(c);
        }
        return c;
    }

    private Set<Class> findClasses(String packageName){
        try {
            return ClassPath.from(ClassLoader.getSystemClassLoader())
                    .getAllClasses()
                    .stream()
                    .filter(clazz -> {
                        boolean flagClazz = clazz.getPackageName().startsWith(packageName);
                        if (!flagClazz){
                            return false;
                        }
                        if (clazz.load().isEnum()){
                            return Arrays.stream(clazz.load().getInterfaces())
                                    .anyMatch(e->BaseEnum.class.getSimpleName().equalsIgnoreCase(e.getSimpleName()));
                        }
                        return false;
                    })
                    .map(clazz -> clazz.load())
                    .collect(Collectors.toSet());
        } catch (IOException e) {
            log.error("扫描指定类型的枚举异常",e);
            return new HashSet<>();
        }
    }
}
